]> dgit.raspbian.org Git - gnome-shell.git/commitdiff
extensions-app: Move classes to their own files
authorFlorian Müllner <fmuellner@gnome.org>
Tue, 19 Dec 2023 14:14:19 +0000 (15:14 +0100)
committerMarge Bot <marge-bot@gnome.org>
Thu, 21 Dec 2023 15:24:23 +0000 (15:24 +0000)
Using a single source file is getting unwieldy, so start
splitting it up.

Part-of: <https://gitlab.gnome.org/GNOME/gnome-shell/-/merge_requests/3067>

po/POTFILES.in
subprojects/extensions-app/js/extensionRow.js [new file with mode: 0644]
subprojects/extensions-app/js/extensionsWindow.js [new file with mode: 0644]
subprojects/extensions-app/js/main.js
subprojects/extensions-app/js/org.gnome.Extensions.src.gresource.xml.in

index 1d9e56948e6bed24bafc1ffc848b2cecd2855b94..f7c05eefc85058458da5b1809b3c2e7c290b486f 100644 (file)
@@ -92,7 +92,8 @@ src/shell-util.c
 src/st/st-icon-theme.c
 subprojects/extensions-app/data/metainfo/org.gnome.Extensions.metainfo.xml.in
 subprojects/extensions-app/data/org.gnome.Extensions.desktop.in.in
-subprojects/extensions-app/js/main.js
+subprojects/extensions-app/js/extensionRow.js
+subprojects/extensions-app/js/extensionsWindow.js
 subprojects/extensions-app/data/ui/extension-row.ui
 subprojects/extensions-app/data/ui/extensions-window.ui
 subprojects/extensions-app/data/ui/help-overlay.ui
diff --git a/subprojects/extensions-app/js/extensionRow.js b/subprojects/extensions-app/js/extensionRow.js
new file mode 100644 (file)
index 0000000..23bed79
--- /dev/null
@@ -0,0 +1,193 @@
+import Adw from 'gi://Adw?version=1';
+import GLib from 'gi://GLib';
+import Gio from 'gi://Gio';
+import GObject from 'gi://GObject';
+
+import {ExtensionState, ExtensionType, deserializeExtension}  from './misc/extensionUtils.js';
+
+export const ExtensionRow = GObject.registerClass({
+    GTypeName: 'ExtensionRow',
+    Template: 'resource:///org/gnome/Extensions/ui/extension-row.ui',
+    InternalChildren: [
+        'detailsPopover',
+        'descriptionLabel',
+        'versionLabel',
+        'errorLabel',
+        'errorButton',
+        'updatesButton',
+        'switch',
+        'actionsBox',
+    ],
+}, class ExtensionRow extends Adw.ActionRow {
+    _init(extension) {
+        super._init();
+
+        this._app = Gio.Application.get_default();
+        this._extension = extension;
+
+        [this._keywords] = GLib.str_tokenize_and_fold(this.name, null);
+
+        this._actionGroup = new Gio.SimpleActionGroup();
+        this.insert_action_group('row', this._actionGroup);
+
+        let action;
+        action = new Gio.SimpleAction({
+            name: 'show-prefs',
+            enabled: this.hasPrefs,
+        });
+        action.connect('activate', () => {
+            this._detailsPopover.popdown();
+            this.get_root().openPrefs(this.uuid);
+        });
+        this._actionGroup.add_action(action);
+
+        action = new Gio.SimpleAction({
+            name: 'show-url',
+            enabled: this.url !== '',
+        });
+        action.connect('activate', () => {
+            this._detailsPopover.popdown();
+            Gio.AppInfo.launch_default_for_uri(
+                this.url, this.get_display().get_app_launch_context());
+        });
+        this._actionGroup.add_action(action);
+
+        action = new Gio.SimpleAction({
+            name: 'uninstall',
+            enabled: this.type === ExtensionType.PER_USER,
+        });
+        action.connect('activate', () => {
+            this._detailsPopover.popdown();
+            this.get_root().uninstall(this.uuid);
+        });
+        this._actionGroup.add_action(action);
+
+        action = new Gio.SimpleAction({
+            name: 'enabled',
+            state: new GLib.Variant('b', false),
+        });
+        action.connect('activate', () => {
+            const state = action.get_state();
+            action.change_state(new GLib.Variant('b', !state.get_boolean()));
+        });
+
+        action.connect('change-state', (a, state) => {
+            if (state.get_boolean())
+                this._app.shellProxy.EnableExtensionAsync(this.uuid).catch(console.error);
+            else
+                this._app.shellProxy.DisableExtensionAsync(this.uuid).catch(console.error);
+        });
+        this._actionGroup.add_action(action);
+
+        this.title = this.name;
+
+        const desc = this._extension.metadata.description.split('\n')[0];
+        this._descriptionLabel.label = desc;
+
+        this.connect('destroy', this._onDestroy.bind(this));
+
+        this._extensionStateChangedId = this._app.shellProxy.connectSignal(
+            'ExtensionStateChanged', (p, sender, [uuid, newState]) => {
+                if (this.uuid !== uuid)
+                    return;
+
+                this._extension = deserializeExtension(newState);
+                this._updateState();
+            });
+        this._updateState();
+    }
+
+    get uuid() {
+        return this._extension.uuid;
+    }
+
+    get name() {
+        return this._extension.metadata.name;
+    }
+
+    get hasPrefs() {
+        return this._extension.hasPrefs;
+    }
+
+    get hasUpdate() {
+        return this._extension.hasUpdate || false;
+    }
+
+    get hasError() {
+        const {state} = this._extension;
+        return state === ExtensionState.OUT_OF_DATE ||
+               state === ExtensionState.ERROR;
+    }
+
+    get type() {
+        return this._extension.type;
+    }
+
+    get creator() {
+        return this._extension.metadata.creator || '';
+    }
+
+    get url() {
+        return this._extension.metadata.url || '';
+    }
+
+    get version() {
+        return this._extension.metadata['version-name'] || this._extension.metadata.version || '';
+    }
+
+    get error() {
+        if (!this.hasError)
+            return '';
+
+        if (this._extension.state === ExtensionState.OUT_OF_DATE) {
+            const {ShellVersion: shellVersion} = this._app.shellProxy;
+            return this.version !== ''
+                ? _('The installed version of this extension (%s) is incompatible with the current version of GNOME (%s). The extension has been disabled.').format(this.version, shellVersion)
+                : _('The installed version of this extension is incompatible with the current version of GNOME (%s). The extension has been disabled.').format(shellVersion);
+        }
+
+        const message = [
+            _('An error has occurred in this extension. This could cause issues elsewhere in the system. It is recommended to turn the extension off until the error is resolved.'),
+        ];
+
+        if (this._extension.error) {
+            message.push(
+                // translators: Details for an extension error
+                _('Error details:'), this._extension.error);
+        }
+
+        return message.join('\n\n');
+    }
+
+    get keywords() {
+        return this._keywords;
+    }
+
+    _updateState() {
+        const state = this._extension.state === ExtensionState.ENABLED;
+
+        const action = this._actionGroup.lookup_action('enabled');
+        action.set_state(new GLib.Variant('b', state));
+        action.enabled = this._extension.canChange;
+
+        if (!action.enabled)
+            this._switch.active = state;
+
+        this._updatesButton.visible = this.hasUpdate;
+        this._errorButton.visible = this.hasError;
+        this._errorLabel.label = this.error;
+
+        this._versionLabel.label = _('Version %s').format(this.version.toString());
+        this._versionLabel.visible = this.version !== '';
+    }
+
+    _onDestroy() {
+        if (!this._app.shellProxy)
+            return;
+
+        if (this._extensionStateChangedId)
+            this._app.shellProxy.disconnectSignal(this._extensionStateChangedId);
+        this._extensionStateChangedId = 0;
+    }
+});
+
diff --git a/subprojects/extensions-app/js/extensionsWindow.js b/subprojects/extensions-app/js/extensionsWindow.js
new file mode 100644 (file)
index 0000000..66671a6
--- /dev/null
@@ -0,0 +1,293 @@
+import Adw from 'gi://Adw?version=1';
+import GLib from 'gi://GLib';
+import Gio from 'gi://Gio';
+import GObject from 'gi://GObject';
+import Gtk from 'gi://Gtk?version=4.0';
+import Shew from 'gi://Shew';
+
+const Package = imports.package;
+import * as Gettext from 'gettext';
+
+import * as Config from './misc/config.js';
+import {ExtensionRow} from './extensionRow.js';
+import {ExtensionState, ExtensionType, deserializeExtension}  from './misc/extensionUtils.js';
+
+Gio._promisify(Gio.DBusConnection.prototype, 'call');
+Gio._promisify(Shew.WindowExporter.prototype, 'export');
+
+export const ExtensionsWindow = GObject.registerClass({
+    GTypeName: 'ExtensionsWindow',
+    Template: 'resource:///org/gnome/Extensions/ui/extensions-window.ui',
+    InternalChildren: [
+        'userGroup',
+        'userList',
+        'systemGroup',
+        'systemList',
+        'mainStack',
+        'searchBar',
+        'searchButton',
+        'searchEntry',
+        'updatesBanner',
+    ],
+}, class ExtensionsWindow extends Adw.ApplicationWindow {
+    _init(params) {
+        super._init(params);
+
+        if (Config.PROFILE === 'development')
+            this.add_css_class('devel');
+
+        this._updatesCheckId = 0;
+
+        this._exporter = new Shew.WindowExporter({window: this});
+        this._exportedHandle = '';
+
+        this.add_action_entries(
+            [{
+                name: 'show-about',
+                activate: () => this._showAbout(),
+            }, {
+                name: 'logout',
+                activate: () => this._logout(),
+            }, {
+                name: 'user-extensions-enabled',
+                state: 'false',
+                change_state: (a, state) => {
+                    this._shellProxy.UserExtensionsEnabled = state.get_boolean();
+                },
+            }]);
+
+        this._searchTerms = [];
+        this._searchEntry.connect('search-changed', () => {
+            const {text} = this._searchEntry;
+            if (text === '')
+                this._searchTerms = [];
+            else
+                [this._searchTerms] = GLib.str_tokenize_and_fold(text, null);
+
+            this._userList.invalidate_filter();
+            this._systemList.invalidate_filter();
+        });
+
+        this._userList.set_sort_func(this._sortList.bind(this));
+        this._userList.set_filter_func(this._filterList.bind(this));
+        this._userList.set_placeholder(new Gtk.Label({
+            label: _('No Matches'),
+            margin_start: 12,
+            margin_end: 12,
+            margin_top: 12,
+            margin_bottom: 12,
+        }));
+        this._userList.connect('row-activated', (_list, row) => row.activate());
+
+        this._systemList.set_sort_func(this._sortList.bind(this));
+        this._systemList.set_filter_func(this._filterList.bind(this));
+        this._systemList.set_placeholder(new Gtk.Label({
+            label: _('No Matches'),
+            margin_start: 12,
+            margin_end: 12,
+            margin_top: 12,
+            margin_bottom: 12,
+        }));
+        this._systemList.connect('row-activated', (_list, row) => row.activate());
+
+        this._shellProxy.connectSignal('ExtensionStateChanged',
+            this._onExtensionStateChanged.bind(this));
+
+        this._shellProxy.connect('g-properties-changed',
+            this._onUserExtensionsEnabledChanged.bind(this));
+        this._onUserExtensionsEnabledChanged();
+
+        this._scanExtensions();
+    }
+
+    get _shellProxy() {
+        return this.application.shellProxy;
+    }
+
+    uninstall(uuid) {
+        const row = this._findExtensionRow(uuid);
+
+        const dialog = new Gtk.MessageDialog({
+            transient_for: this,
+            modal: true,
+            text: _('Remove “%s”?').format(row.name),
+            secondary_text: _('If you remove the extension, you need to return to download it if you want to enable it again'),
+        });
+
+        dialog.add_button(_('_Cancel'), Gtk.ResponseType.CANCEL);
+        dialog.add_button(_('_Remove'), Gtk.ResponseType.ACCEPT)
+            .get_style_context().add_class('destructive-action');
+
+        dialog.connect('response', (dlg, response) => {
+            if (response === Gtk.ResponseType.ACCEPT)
+                this._shellProxy.UninstallExtensionAsync(uuid).catch(console.error);
+            dialog.destroy();
+        });
+        dialog.present();
+    }
+
+    async openPrefs(uuid) {
+        if (!this._exportedHandle) {
+            try {
+                this._exportedHandle = await this._exporter.export();
+            } catch (e) {
+                console.warn(`Failed to export window: ${e.message}`);
+            }
+        }
+
+        this._shellProxy.OpenExtensionPrefsAsync(uuid,
+            this._exportedHandle,
+            {modal: new GLib.Variant('b', true)}).catch(console.error);
+    }
+
+    _showAbout() {
+        const aboutWindow = new Adw.AboutWindow({
+            developers: [
+                'Florian Müllner <fmuellner@gnome.org>',
+                'Jasper St. Pierre <jstpierre@mecheye.net>',
+                'Didier Roche <didrocks@ubuntu.com>',
+                'Romain Vigier <contact@romainvigier.fr>',
+            ],
+            designers: [
+                'Allan Day <allanpday@gmail.com>',
+                'Tobias Bernard <tbernard@gnome.org>',
+            ],
+            translator_credits: _('translator-credits'),
+            application_name: _('Extensions'),
+            license_type: Gtk.License.GPL_2_0,
+            application_icon: Package.name,
+            version: Package.version,
+            developer_name: _('The GNOME Project'),
+            website: 'https://apps.gnome.org/app/org.gnome.Extensions/',
+            issue_url: 'https://gitlab.gnome.org/GNOME/gnome-shell/issues/new',
+
+            transient_for: this,
+        });
+        aboutWindow.present();
+    }
+
+    _logout() {
+        this.application.get_dbus_connection().call(
+            'org.gnome.SessionManager',
+            '/org/gnome/SessionManager',
+            'org.gnome.SessionManager',
+            'Logout',
+            new GLib.Variant('(u)', [0]),
+            null,
+            Gio.DBusCallFlags.NONE,
+            -1,
+            null);
+    }
+
+    _sortList(row1, row2) {
+        return row1.name.localeCompare(row2.name);
+    }
+
+    _filterList(row) {
+        return this._searchTerms.every(
+            t => row.keywords.some(k => k.startsWith(t)));
+    }
+
+    _findExtensionRow(uuid) {
+        return [
+            ...this._userList,
+            ...this._systemList,
+        ].find(c => c.uuid === uuid);
+    }
+
+    _onUserExtensionsEnabledChanged() {
+        const action = this.lookup_action('user-extensions-enabled');
+        action.set_state(
+            new GLib.Variant('b', this._shellProxy.UserExtensionsEnabled));
+    }
+
+    _onExtensionStateChanged(proxy, senderName, [uuid, newState]) {
+        const extension = deserializeExtension(newState);
+        let row = this._findExtensionRow(uuid);
+
+        this._queueUpdatesCheck();
+
+        // the extension's type changed; remove the corresponding row
+        // and reset the variable to null so that we create a new row
+        // below and add it to the appropriate list
+        if (row && row.type !== extension.type) {
+            row.get_parent().remove(row);
+            row = null;
+        }
+
+        if (row) {
+            if (extension.state === ExtensionState.UNINSTALLED)
+                row.get_parent().remove(row);
+        } else {
+            this._addExtensionRow(extension);
+        }
+
+        this._syncListVisibility();
+    }
+
+    async _scanExtensions() {
+        try {
+            const [extensionsMap] = await this._shellProxy.ListExtensionsAsync();
+
+            for (let uuid in extensionsMap) {
+                const extension = deserializeExtension(extensionsMap[uuid]);
+                this._addExtensionRow(extension);
+            }
+            this._extensionsLoaded();
+        } catch (e) {
+            if (e instanceof Gio.DBusError) {
+                console.log(`Failed to connect to shell proxy: ${e}`);
+                this._mainStack.visible_child_name = 'noshell';
+            } else {
+                throw e;
+            }
+        }
+    }
+
+    _addExtensionRow(extension) {
+        const row = new ExtensionRow(extension);
+
+        if (row.type === ExtensionType.PER_USER)
+            this._userList.append(row);
+        else
+            this._systemList.append(row);
+    }
+
+    _queueUpdatesCheck() {
+        if (this._updatesCheckId)
+            return;
+
+        this._updatesCheckId = GLib.timeout_add_seconds(
+            GLib.PRIORITY_DEFAULT, 1, () => {
+                this._checkUpdates();
+
+                this._updatesCheckId = 0;
+                return GLib.SOURCE_REMOVE;
+            });
+    }
+
+    _syncListVisibility() {
+        this._userGroup.visible = [...this._userList].length > 1;
+        this._systemGroup.visible = [...this._systemList].length > 1;
+
+        if (this._userGroup.visible || this._systemGroup.visible)
+            this._mainStack.visible_child_name = 'main';
+        else
+            this._mainStack.visible_child_name = 'placeholder';
+    }
+
+    _checkUpdates() {
+        const nUpdates = [...this._userList].filter(c => c.hasUpdate).length;
+
+        this._updatesBanner.title = Gettext.ngettext(
+            '%d extension will be updated on next login.',
+            '%d extensions will be updated on next login.',
+            nUpdates).format(nUpdates);
+        this._updatesBanner.revealed = nUpdates > 0;
+    }
+
+    _extensionsLoaded() {
+        this._syncListVisibility();
+        this._checkUpdates();
+    }
+});
index 696f29b5734863d3ed2e25d2bae2399aac0aa942..47f0d061ad34bfd070384e8c79df4ea4909a5630 100644 (file)
@@ -2,26 +2,17 @@ import Adw from 'gi://Adw?version=1';
 import GLib from 'gi://GLib';
 import Gio from 'gi://Gio';
 import GObject from 'gi://GObject';
-import Gtk from 'gi://Gtk?version=4.0';
-import Shew from 'gi://Shew';
 
 import {setConsoleLogDomain} from 'console';
-import * as Gettext from 'gettext';
 const Package = imports.package;
 
 Package.initFormat();
 
-import * as Config from './misc/config.js';
-import * as ExtensionUtils from './misc/extensionUtils.js';
-
-import {ExtensionState, ExtensionType}  from './misc/extensionUtils.js';
+import {ExtensionsWindow} from './extensionsWindow.js';
 
 const GnomeShellIface = loadInterfaceXML('org.gnome.Shell.Extensions');
 const GnomeShellProxy = Gio.DBusProxy.makeProxyWrapper(GnomeShellIface);
 
-Gio._promisify(Gio.DBusConnection.prototype, 'call');
-Gio._promisify(Shew.WindowExporter.prototype, 'export');
-
 function loadInterfaceXML(iface) {
     const uri = `resource:///org/gnome/Extensions/dbus-interfaces/${iface}.xml`;
     const f = Gio.File.new_for_uri(uri);
@@ -36,11 +27,6 @@ function loadInterfaceXML(iface) {
     return null;
 }
 
-function toggleState(action) {
-    let state = action.get_state();
-    action.change_state(new GLib.Variant('b', !state.get_boolean()));
-}
-
 var Application = GObject.registerClass(
 class Application extends Adw.Application {
     _init() {
@@ -77,470 +63,6 @@ class Application extends Adw.Application {
     }
 });
 
-var ExtensionsWindow = GObject.registerClass({
-    GTypeName: 'ExtensionsWindow',
-    Template: 'resource:///org/gnome/Extensions/ui/extensions-window.ui',
-    InternalChildren: [
-        'userGroup',
-        'userList',
-        'systemGroup',
-        'systemList',
-        'mainStack',
-        'searchBar',
-        'searchButton',
-        'searchEntry',
-        'updatesBanner',
-    ],
-}, class ExtensionsWindow extends Adw.ApplicationWindow {
-    _init(params) {
-        super._init(params);
-
-        if (Config.PROFILE === 'development')
-            this.add_css_class('devel');
-
-        this._updatesCheckId = 0;
-
-        this._exporter = new Shew.WindowExporter({window: this});
-        this._exportedHandle = '';
-
-        this.add_action_entries(
-            [{
-                name: 'show-about',
-                activate: () => this._showAbout(),
-            }, {
-                name: 'logout',
-                activate: () => this._logout(),
-            }, {
-                name: 'user-extensions-enabled',
-                state: 'false',
-                change_state: (a, state) => {
-                    this._shellProxy.UserExtensionsEnabled = state.get_boolean();
-                },
-            }]);
-
-        this._searchTerms = [];
-        this._searchEntry.connect('search-changed', () => {
-            const {text} = this._searchEntry;
-            if (text === '')
-                this._searchTerms = [];
-            else
-                [this._searchTerms] = GLib.str_tokenize_and_fold(text, null);
-
-            this._userList.invalidate_filter();
-            this._systemList.invalidate_filter();
-        });
-
-        this._userList.set_sort_func(this._sortList.bind(this));
-        this._userList.set_filter_func(this._filterList.bind(this));
-        this._userList.set_placeholder(new Gtk.Label({
-            label: _('No Matches'),
-            margin_start: 12,
-            margin_end: 12,
-            margin_top: 12,
-            margin_bottom: 12,
-        }));
-        this._userList.connect('row-activated', (_list, row) => row.activate());
-
-        this._systemList.set_sort_func(this._sortList.bind(this));
-        this._systemList.set_filter_func(this._filterList.bind(this));
-        this._systemList.set_placeholder(new Gtk.Label({
-            label: _('No Matches'),
-            margin_start: 12,
-            margin_end: 12,
-            margin_top: 12,
-            margin_bottom: 12,
-        }));
-        this._systemList.connect('row-activated', (_list, row) => row.activate());
-
-        this._shellProxy.connectSignal('ExtensionStateChanged',
-            this._onExtensionStateChanged.bind(this));
-
-        this._shellProxy.connect('g-properties-changed',
-            this._onUserExtensionsEnabledChanged.bind(this));
-        this._onUserExtensionsEnabledChanged();
-
-        this._scanExtensions();
-    }
-
-    get _shellProxy() {
-        return this.application.shellProxy;
-    }
-
-    uninstall(uuid) {
-        let row = this._findExtensionRow(uuid);
-
-        let dialog = new Gtk.MessageDialog({
-            transient_for: this,
-            modal: true,
-            text: _('Remove “%s”?').format(row.name),
-            secondary_text: _('If you remove the extension, you need to return to download it if you want to enable it again'),
-        });
-
-        dialog.add_button(_('_Cancel'), Gtk.ResponseType.CANCEL);
-        dialog.add_button(_('_Remove'), Gtk.ResponseType.ACCEPT)
-            .get_style_context().add_class('destructive-action');
-
-        dialog.connect('response', (dlg, response) => {
-            if (response === Gtk.ResponseType.ACCEPT)
-                this._shellProxy.UninstallExtensionAsync(uuid).catch(console.error);
-            dialog.destroy();
-        });
-        dialog.present();
-    }
-
-    async openPrefs(uuid) {
-        if (!this._exportedHandle) {
-            try {
-                this._exportedHandle = await this._exporter.export();
-            } catch (e) {
-                console.warn(`Failed to export window: ${e.message}`);
-            }
-        }
-
-        this._shellProxy.OpenExtensionPrefsAsync(uuid,
-            this._exportedHandle,
-            {modal: new GLib.Variant('b', true)}).catch(console.error);
-    }
-
-    _showAbout() {
-        let aboutWindow = new Adw.AboutWindow({
-            developers: [
-                'Florian Müllner <fmuellner@gnome.org>',
-                'Jasper St. Pierre <jstpierre@mecheye.net>',
-                'Didier Roche <didrocks@ubuntu.com>',
-                'Romain Vigier <contact@romainvigier.fr>',
-            ],
-            designers: [
-                'Allan Day <allanpday@gmail.com>',
-                'Tobias Bernard <tbernard@gnome.org>',
-            ],
-            translator_credits: _('translator-credits'),
-            application_name: _('Extensions'),
-            license_type: Gtk.License.GPL_2_0,
-            application_icon: Package.name,
-            version: Package.version,
-            developer_name: _('The GNOME Project'),
-            website: 'https://apps.gnome.org/app/org.gnome.Extensions/',
-            issue_url: 'https://gitlab.gnome.org/GNOME/gnome-shell/issues/new',
-
-            transient_for: this,
-        });
-        aboutWindow.present();
-    }
-
-    _logout() {
-        this.application.get_dbus_connection().call(
-            'org.gnome.SessionManager',
-            '/org/gnome/SessionManager',
-            'org.gnome.SessionManager',
-            'Logout',
-            new GLib.Variant('(u)', [0]),
-            null,
-            Gio.DBusCallFlags.NONE,
-            -1,
-            null);
-    }
-
-    _sortList(row1, row2) {
-        return row1.name.localeCompare(row2.name);
-    }
-
-    _filterList(row) {
-        return this._searchTerms.every(
-            t => row.keywords.some(k => k.startsWith(t)));
-    }
-
-    _findExtensionRow(uuid) {
-        return [
-            ...this._userList,
-            ...this._systemList,
-        ].find(c => c.uuid === uuid);
-    }
-
-    _onUserExtensionsEnabledChanged() {
-        let action = this.lookup_action('user-extensions-enabled');
-        action.set_state(
-            new GLib.Variant('b', this._shellProxy.UserExtensionsEnabled));
-    }
-
-    _onExtensionStateChanged(proxy, senderName, [uuid, newState]) {
-        let extension = ExtensionUtils.deserializeExtension(newState);
-        let row = this._findExtensionRow(uuid);
-
-        this._queueUpdatesCheck();
-
-        // the extension's type changed; remove the corresponding row
-        // and reset the variable to null so that we create a new row
-        // below and add it to the appropriate list
-        if (row && row.type !== extension.type) {
-            row.get_parent().remove(row);
-            row = null;
-        }
-
-        if (row) {
-            if (extension.state === ExtensionState.UNINSTALLED)
-                row.get_parent().remove(row);
-        } else {
-            this._addExtensionRow(extension);
-        }
-
-        this._syncListVisibility();
-    }
-
-    async _scanExtensions() {
-        try {
-            const [extensionsMap] = await this._shellProxy.ListExtensionsAsync();
-
-            for (let uuid in extensionsMap) {
-                let extension = ExtensionUtils.deserializeExtension(extensionsMap[uuid]);
-                this._addExtensionRow(extension);
-            }
-            this._extensionsLoaded();
-        } catch (e) {
-            if (e instanceof Gio.DBusError) {
-                console.log(`Failed to connect to shell proxy: ${e}`);
-                this._mainStack.visible_child_name = 'noshell';
-            } else {
-                throw e;
-            }
-        }
-    }
-
-    _addExtensionRow(extension) {
-        let row = new ExtensionRow(extension);
-
-        if (row.type === ExtensionType.PER_USER)
-            this._userList.append(row);
-        else
-            this._systemList.append(row);
-    }
-
-    _queueUpdatesCheck() {
-        if (this._updatesCheckId)
-            return;
-
-        this._updatesCheckId = GLib.timeout_add_seconds(
-            GLib.PRIORITY_DEFAULT, 1, () => {
-                this._checkUpdates();
-
-                this._updatesCheckId = 0;
-                return GLib.SOURCE_REMOVE;
-            });
-    }
-
-    _syncListVisibility() {
-        this._userGroup.visible = [...this._userList].length > 1;
-        this._systemGroup.visible = [...this._systemList].length > 1;
-
-        if (this._userGroup.visible || this._systemGroup.visible)
-            this._mainStack.visible_child_name = 'main';
-        else
-            this._mainStack.visible_child_name = 'placeholder';
-    }
-
-    _checkUpdates() {
-        let nUpdates = [...this._userList].filter(c => c.hasUpdate).length;
-
-        this._updatesBanner.title = Gettext.ngettext(
-            '%d extension will be updated on next login.',
-            '%d extensions will be updated on next login.',
-            nUpdates).format(nUpdates);
-        this._updatesBanner.revealed = nUpdates > 0;
-    }
-
-    _extensionsLoaded() {
-        this._syncListVisibility();
-        this._checkUpdates();
-    }
-});
-
-var ExtensionRow = GObject.registerClass({
-    GTypeName: 'ExtensionRow',
-    Template: 'resource:///org/gnome/Extensions/ui/extension-row.ui',
-    InternalChildren: [
-        'detailsPopover',
-        'descriptionLabel',
-        'versionLabel',
-        'errorLabel',
-        'errorButton',
-        'updatesButton',
-        'switch',
-        'actionsBox',
-    ],
-}, class ExtensionRow extends Adw.ActionRow {
-    _init(extension) {
-        super._init();
-
-        this._app = Gio.Application.get_default();
-        this._extension = extension;
-        this._prefsModule = null;
-
-        [this._keywords] = GLib.str_tokenize_and_fold(this.name, null);
-
-        this._actionGroup = new Gio.SimpleActionGroup();
-        this.insert_action_group('row', this._actionGroup);
-
-        let action;
-        action = new Gio.SimpleAction({
-            name: 'show-prefs',
-            enabled: this.hasPrefs,
-        });
-        action.connect('activate', () => {
-            this._detailsPopover.popdown();
-            this.get_root().openPrefs(this.uuid);
-        });
-        this._actionGroup.add_action(action);
-
-        action = new Gio.SimpleAction({
-            name: 'show-url',
-            enabled: this.url !== '',
-        });
-        action.connect('activate', () => {
-            this._detailsPopover.popdown();
-            Gio.AppInfo.launch_default_for_uri(
-                this.url, this.get_display().get_app_launch_context());
-        });
-        this._actionGroup.add_action(action);
-
-        action = new Gio.SimpleAction({
-            name: 'uninstall',
-            enabled: this.type === ExtensionType.PER_USER,
-        });
-        action.connect('activate', () => {
-            this._detailsPopover.popdown();
-            this.get_root().uninstall(this.uuid);
-        });
-        this._actionGroup.add_action(action);
-
-        action = new Gio.SimpleAction({
-            name: 'enabled',
-            state: new GLib.Variant('b', false),
-        });
-        action.connect('activate', toggleState);
-        action.connect('change-state', (a, state) => {
-            if (state.get_boolean())
-                this._app.shellProxy.EnableExtensionAsync(this.uuid).catch(console.error);
-            else
-                this._app.shellProxy.DisableExtensionAsync(this.uuid).catch(console.error);
-        });
-        this._actionGroup.add_action(action);
-
-        this.title = this.name;
-
-        const desc = this._extension.metadata.description.split('\n')[0];
-        this._descriptionLabel.label = desc;
-
-        this.connect('destroy', this._onDestroy.bind(this));
-
-        this._extensionStateChangedId = this._app.shellProxy.connectSignal(
-            'ExtensionStateChanged', (p, sender, [uuid, newState]) => {
-                if (this.uuid !== uuid)
-                    return;
-
-                this._extension = ExtensionUtils.deserializeExtension(newState);
-                this._updateState();
-            });
-        this._updateState();
-    }
-
-    get uuid() {
-        return this._extension.uuid;
-    }
-
-    get name() {
-        return this._extension.metadata.name;
-    }
-
-    get hasPrefs() {
-        return this._extension.hasPrefs;
-    }
-
-    get hasUpdate() {
-        return this._extension.hasUpdate || false;
-    }
-
-    get hasError() {
-        const {state} = this._extension;
-        return state === ExtensionState.OUT_OF_DATE ||
-               state === ExtensionState.ERROR;
-    }
-
-    get type() {
-        return this._extension.type;
-    }
-
-    get creator() {
-        return this._extension.metadata.creator || '';
-    }
-
-    get url() {
-        return this._extension.metadata.url || '';
-    }
-
-    get version() {
-        return this._extension.metadata['version-name'] || this._extension.metadata.version || '';
-    }
-
-    get error() {
-        if (!this.hasError)
-            return '';
-
-        if (this._extension.state === ExtensionState.OUT_OF_DATE) {
-            const {ShellVersion: shellVersion} = this._app.shellProxy;
-            return this.version !== ''
-                ? _('The installed version of this extension (%s) is incompatible with the current version of GNOME (%s). The extension has been disabled.').format(this.version, shellVersion)
-                : _('The installed version of this extension is incompatible with the current version of GNOME (%s). The extension has been disabled.').format(shellVersion);
-        }
-
-        const message = [
-            _('An error has occurred in this extension. This could cause issues elsewhere in the system. It is recommended to turn the extension off until the error is resolved.'),
-        ];
-
-        if (this._extension.error) {
-            message.push(
-                // translators: Details for an extension error
-                _('Error details:'), this._extension.error);
-        }
-
-        return message.join('\n\n');
-    }
-
-    get keywords() {
-        return this._keywords;
-    }
-
-    _updateState() {
-        let state = this._extension.state === ExtensionState.ENABLED;
-
-        let action = this._actionGroup.lookup('enabled');
-        action.set_state(new GLib.Variant('b', state));
-        action.enabled = this._canToggle();
-
-        if (!action.enabled)
-            this._switch.active = state;
-
-        this._updatesButton.visible = this.hasUpdate;
-        this._errorButton.visible = this.hasError;
-        this._errorLabel.label = this.error;
-
-        this._versionLabel.label = _('Version %s').format(this.version.toString());
-        this._versionLabel.visible = this.version !== '';
-    }
-
-    _onDestroy() {
-        if (!this._app.shellProxy)
-            return;
-
-        if (this._extensionStateChangedId)
-            this._app.shellProxy.disconnectSignal(this._extensionStateChangedId);
-        this._extensionStateChangedId = 0;
-    }
-
-    _canToggle() {
-        return this._extension.canChange;
-    }
-});
-
 /**
  * Main entrypoint for the app
  *
index 330ede10068f6bf3de4080d60c2de371af6814d0..eb7759aec6ad1396cd33aecfbf570d73ab6dc95d 100644 (file)
@@ -1,6 +1,8 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <gresources>
   <gresource prefix="/org/gnome/Extensions@profile@/js">
+    <file>extensionRow.js</file>
+    <file>extensionsWindow.js</file>
     <file>main.js</file>
 
     <file>misc/config.js</file>